#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##-- G05 -- S05 ------------------------------------------------------------##
__all__ = []


from .. import autostacks
from .. import formlib as layout
from .. import pilelib as stack
from ..forms import freecell as FreeCellLayout, canfield as CanfieldLayout
from ..gamelib import info as gi, state, game
from ..hint import CautiousDefaultHint
from ..packlib import info as cs, place
from ..utillib.misc import kwdefault
from ..pilelib import move as a_move
from . import canfield
from . import terrace

GT, SL = gi.GT, gi.SL
Wrap = stack.StackWrapper


class GrandDuchess_Talon(stack.DealRowRedealTalon):

    @property
    def _pilesHaveCards(self):
        """
        _pilesHaveCards -> int

        :return: Game.s.rows or Game.s.reserves has cards.
        :rtype: integer
        """
        return bool(sum(len(r.cards) for r in self._piles +
                                                self.game.s['reserves']))

    def deal_cards(self, rows=None, shuffle=False, sound=False, reverse=False,
                  **kw):
        """
        deal_cards([sound:bool=False, shuffle=False, reverse=False]) -> int

        Actual dealing, called from game.deal_cards(). Overide default, to
        allow cards to be dealt a set to and redealt from _piles and
        Game.s.reserves. Cards dealt to Game.s.reserves are dealt face down.

        :keyword boolean sound: sound fx?
        :keyword boolean shuffle: shuffle cards before redealing?
        :keyword boolean reverse: deals cards in reverse order?
        :return: number of cards dealt.
        """
        ncards = 0
        game = self.game
        reserves = game.s['reserves']
        if sound and game.can_play_sound:
            game._play_sound('deal')
        if not self.cards:
            ncards += self._redeal(rows=self._piles + reserves, frames=4)
            if shuffle:
                game._move_do(a_move.AShuffleStackMove(self, game.random)
                              )  # low level
            if reverse:
                self.cards.reverse()
            game._move_do(a_move.ANextRoundMove(self), reset=True)  # low level
        for i in range(self.num_deal):
            if self.round != 4:
                ncards += self.dealRowAvail(rows=reserves[:1], flip=False)
            ncards += self.dealRowAvail(rows=self._piles, **kw)
            if self.round != 4:
                ncards += self.dealRowAvail(rows=reserves[1:], flip=False)
        return ncards


class DoubleFives_Talon(stack.RedealTalon):

    def can_deal_cards(self):
        """
        can_deal_cards() -> bool

        Check by Game.deal_cards() if pile can deal more cards. Default is
        while there are cards available.

        :return: deal more cards?
        :rtype: True | False
        """
        return (not self.game.s['foundations'][0].caps['baserank'] == cs.ANY and
                super(DoubleFives_Talon, self).can_deal_cards())

    @state.move_state('deal')
    def deal_cards(self, sound=False):
        """
        deal_cards([sound:bool=False, shuffle=False, reverse=False]) -> int

        Actual dealing, called from game.deal_cards(). Overide default, to
        allow cards to be dealt either to set of waste a picked up or a waste.

        :keyword boolean sound: sound fx?
        :keyword boolean shuffle: shuffle cards before redealing?
        :keyword boolean reverse: deals cards in reverse order?
        :return: number of cards dealt.
        """
        ncards = 0
        if self.round == 1:
            game = self.game
            # Round 1 deal each xwastes
            if sound and game.can_play_sound:
                game._play_sound('deal')
            waste, xwastes = game.s['wastes'][-1:], game.s['wastes'][:-1]
            # move xwaste to waste
            for pile in xwastes:
                if pile.cards:
                    game._move_do(a_move.AFlipAndMoveMove(stack, waste),
                                   reset=True)  # low-level
            # redeal when no cards else deal xwastes
            if not self.cards:
                ncards += self.redeal_cards(rows=waste, frames=4, sound=False)
            else:
                ncards += self.dealRowAvail(rows=xwastes, sound=False)
        else:
            if sound and game.can_play_sound:
                game._play_sound('dealwaste')
            game._move_do(a_move.AFlipAndMoveMove(self, game.s['waste'][0],
                                                  frames=4),
                            reset=True)  # low-level
            ncards += 1
        return ncards


class ReserveWaste(stack.WasteStack):

    @state.not_state('is_preview')
    def _update_text(self):
        """
        _update_text() -> None

        Update number of cards on a stack only on 2nd turn.
        """
        if self.game.s['talon'].round == self.game.s['talon'].max_rounds:
            super(ReserveWaste, self)._update_text()
        elif self.texts['ncards']:
            self.texts['ncards'].config(text='')


class Waste(stack.WasteStack):

    def cardFlips(self):
        """
        cardFlips() -> bool

        Pile cannot flip cards.

        :return: pile flips top card?
        :rtype: boolean
        """
        return False

    @state.not_state('is_preview')
    def _update_text(self):
        """
        _update_text() -> None

        Update number of cards while stack has cards.
        """
        if self.cards:
            super(Waste, self)._update_text()
        else:
            self.texts['ncards'].config(text='')


class Dutchess_RowStack(stack.AC_RowStack):
    """
    Build down in rank by alt color from any but _reserve_ after card finish
    on _foundation_. _BOTTOM_IMAGE_ is shade.
    """

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:tup) -> bool

        Check if a _foundation_ pile has a card or no from _reserve_ and
        parent can build with cards from from_stack.

        :param object from_stack: pile playing cards.
        :param tuple cards: cards playing on pile.
        :return: pile can build with cards from played from_stack?
        :rtype: boolean

        seealso:
          Method pysollib.stackcaps.builds for the *capability* check.
        """
        if not (self.game.s['foundations'][0].caps['baserank'] != cs.ANY or
                super(Dutchess_RowStack, self).cardsBuild(from_stack, cards)):
            return False
        if not self.cards and from_stack in self.game.s['wastes']:
            return not any(s['cards'] for s in self.game.s['reserves'])
        return len(cards) == 1 or len(self.cards) == len(cards)


class GrandDuchess_Reserve(stack.ArbitraryStack):

    def cardFlips(self):
        """
        cardFlips() -> bool

        Pile cannot flip cards.

        :return: pile flips top card?
        :rtype: boolean
        """
        return False


class GrandDuchess(game.Game):
    """
    Finish cards on _foundation_ within 4 turn.

    _row_
      Pile 4 horz S _foundation_. Init deal 1 card. Build none. Play top.

    _reserve_
      Pile 2 E W _row_. Init deal 1 close card. Build none. Play top. Flip all
      after _talon_ gap.

    _talon_
      Pile 1 NW. Deal 1 card on _row_ and 1 close card on _reserve_.

    _foundation_
      Pile 8 horz N. Build in rank by same suit. N up, S down. Play top.

    seealso::
      http://en.wikipedia.org/wiki/Grand_Duchess_%28solitaire%29
    ------
    :cvar Stack _talon_: GrandDuchess_Talon (max_rounds=4)
    :cvar Stack _foundation_: stack.SS_Foundation,
      stack.SS_Foundation (base_rank=KING, dir=-1)
    :cvar Stack _row_: stack.BasicRowStack
    :cvar Stack _reserve_: GrandDuchess_Reserve
    :cvar Hint _hint_: hint.DefaultHint
    """
    __info__ = dict(id=557, name='Grand Duchess', game_type=GT.TWO_DECK,
                    decks=2, redeals=3, altnames=('Duchess de Luynes',),
                    version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = Wrap(GrandDuchess_Talon, max_rounds=4)
    _foundation_ = (stack.SS_Foundation,
                    Wrap(stack.SS_Foundation, base_rank=cs.KING,
                                 dir=-1),)
    _row_ = stack.BasicRowStack
    _reserve_ = GrandDuchess_Reserve
    _layout_ = CanfieldLayout.DoorwayLayout

    def _create_game(self, **kw):
        """
        Create game format.

        Augment pile placements based on current form and group attributes.

        .. \_create_game(rows=4, playcards=18) -> Layout
        """
        kwdefault(kw, rows=4, playcards=18)
        return super(GrandDuchess, self)._create_game(self, **kw)

    def _start(self, **kw):
        """
        Deal initial cards for new game.
          - s[reserves][0]: 1 ea.
          - s[rows]: 1 ea.
          - s[reserves][1]: 1 ea.

        .. \_start([]) -> None
        """
        self._play_sound('deal')
        self.s['talon'].dealRow(rows=[self.s['reserves'][0]], flip=False)
        self.s['talon'].dealRow()
        self.s['talon'].dealRow(rows=[self.s['reserves'][1]], flip=False)

    def _autopiles(self, event=None):
        """
        Return piles for auto or quick move.

        Overide to disable autoflip, autodrop.

        .. \_autopiles(self, event=None) -> seq(seq(Pile ...), seq(Pile ...),
             seq(Pile ...))
        """
        return autostacks.quickplay(super(GrandDuchess, self)._autopiles(event))


class Parisienne(GrandDuchess):
    """
    Finish cards on _foundation_ within 4 turn.

    _row_
      Pile 4 horz S _foundation_. Init deal 1 card. Build none. Play top.

    _reserve_
      Pile 2 E W _row_. Init deal 1 close card. Build none. Play top. Flip all
      after _talon_ gap.

    _talon_
      Pile 1 NW. Deal 1 card on _row_ and 1 close card on _reserve_.

    _foundation_
      Pile 8 horz N. Init deal N base ACE and S base KING. Build in rank by
      same suit. N up, S down. Play top.

    seealso::
      http://en.wikipedia.org/wiki/Grand_Duchess_%28solitaire%29
    ------
    :cvar Stack _talon_: GrandDuchess_Talon (max_rounds=4)
    :cvar Stack _foundation_: stack.SS_Foundation,
      stack.SS_Foundation (base_rank=KING, dir=-1)
    :cvar Stack _row_: stack.BasicRowStack
    :cvar Stack _reserve_: GrandDuchess_Reserve
    :cvar Hint _hint_: hint.DefaultHint
    """
    __info__ = dict(id=617, name='Parisienne', game_type=GT.TWO_DECK,
                    decks=2, redeals=3, altnames=('La Parisienne',
                    'Parisian'), version='0.93fc')

    def _pack_place(self, cards):
        """
        _pack_place(cards:seq(Card ...)) -> seq(Card ...)

        Overide to return ACE and KING of suit on top by rank, suit, deck for
        _foundation_ deal.
        """
        return place.MoveToTop(cards, place.OneAceKing)

    def _start(self, **kw):
        """
        Deal initial cards for new game.
          - s[foundations]: 1 ea.
          - s[reserves][0]: 1 ea.
          - s[rows]: 1 ea.
          - s[reserves][1]: 1 ea.

        .. \_start([]) -> None
        """
        self.s['talon'].dealRow(rows=self.s['foundations'], frames=0)
        super(Parisienne, self)._start(self, **kw)


class GrandDuchessPlus(GrandDuchess):
    """
    Finish cards on _foundation_ within 4 turn.

    _row_
      Pile 6 horz S _foundation_. Init deal 1 card. Build none. Play top.

    _reserve_
      Pile 2 E W _row_. Init deal 1 close card. Build none. Play top. Flip all
      after _talon_ gap.

    _talon_
      Pile 1 NW. Deal 1 card on _row_ and 1 close card on _reserve_.

    _foundation_
      Pile 8 horz N. Build in rank by same suit. N up, S down. Play top.
    ------
    :cvar Stack _talon_: GrandDuchess_Talon (max_rounds=4)
    :cvar Stack _foundation_: stack.SS_Foundation,
      stack.SS_Foundation (base_rank=KING, dir=-1)
    :cvar Stack _row_: stack.BasicRowStack
    :cvar Stack _reserve_: GrandDuchess_Reserve
    :cvar Hint _hint_: hint.DefaultHint
    """
    __info__ = dict(id=618, name='Grand Duchess +', game_type=GT.TWO_DECK,
                    decks=2, redeals=3, version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    def _create_game(self, **kw):
        """
        Create game format.

        Augment pile placements based on current form and group attributes.

        .. \_create_game(rows=6) -> Layout
        """
        kw.setdefault('rows', 6)
        return super(GrandDuchessPlus, self)._create_game(**kw)


class Dutchess(game.Game):
    """
    Finish cards on _foundation_ within 2 turn.

    _row_
      Pile 4 horz S _foundation_. Init deal 1 card. Build down in rank by
      alt color wrap. Play seq.

    _reserve_
      Pile 4 vert W. Init deal 4 open card. Build none. Play top. 1 card must
      finish before any play.

    _talon_
      Pile 1 SW. Deal 1 card on NW _wastes_.

    _waste_
      Pile 1 E _talon_. Build none. Play top.

    _foundation_
      Pile 1 N. Build up in rank by same suit from base. Play none.
    ------
    :cvar Stack _talon_: terrace.Terrace_Talon (max_rounds=2)
    :cvar Stack _waste_: stack.WasteStack
    :cvar Stack _foundation_: stack.Vary_SS_Foundation (max_move=0)
    :cvar Stack _row_: Dutchess_RowStack (mod=13)
    :cvar Stack _reserve_: stack.OpenStack
    :cvar Hint _hint_: canfield.Canfield_Hint
    """
    __info__ = dict(id=282, name='Dutchess', game_type=(GT.CANFIELD |
                    GT.GNOME), decks=1, redeals=1, skill_level=SL.BALANCED,
                    altnames=('Duchess', 'Glenwood',), version='0.80fc')
    __author__ = 'Skomoroh'
    __credits__ = ['Albert Morehead', 'Geoffrey Mott-Smith']
    __version__ = 7.70

    _talon_ = Wrap(terrace.Terrace_Talon, max_rounds=2)
    _waste_ = stack.WasteStack
    _foundation_ = Wrap(stack.Vary_SS_Foundation, max_move=0)
    _row_ = Wrap(Dutchess_RowStack, mod=13)
    _reserve_ = stack.OpenStack
    _hint_ = canfield.Hint

    def _create_game(self, rows=4, reserves=4, playcards=11, **kw):
        """
        Create game format.

        Override pile placements based on current group attributes alone.

        .. \_create_game(rows=4, reserves=4, playcards=11) -> Layout
        """
        l, s = layout.Layout(self), self.s
        max_rows = max(3 + l.NSUITS, rows + 3)
        max_cols = max(reserves * l.YS, l._get_stack_height(playcards))
        self.table_size = (l.XM + max_rows * l.XS,
                           l.YM + l.TEXT_HEIGHT + l.YS + max_cols,)
        x, y = l.XM, l.YM
        s['talon'] = self._talon_(x, y, game=self)
        l.set_ncards_text(s['talon'], 's')
        l.set_rounds_text(s['talon'], 'ne', dx=l.XS)
        x += l.XS
        s['wastes'] = [self._waste_(x, y, game=self)]
        l.set_ncards_text(s['waste'][-1], 's')
        dx = l.XM + 3 * l.XS
        s['foundations'] = [self._foundation_(x, y, game=self, suit=s)
                            for x, s in zip(l.row_iter(dx, l.NSUITS), l.SUITS)]
        self.texts['info'] = l.create_info_text(s['foundations'][-1],
                                                anchor='ss')
        y += l.TEXT_HEIGHT + l.YS
        s['rows'] = [self._row_(x, y, game=self) for x in l.row_iter(dx, rows)]
        x, dy = l.XM, y
        s['reserves'] = [self._reserve_(x, y, game=self, offset=(l.XOFFSET, 0))
                         for y in l.col_iter(dy, reserves)]
        l.defaultStackGroups()
        return l

    def _start(self, reserves=4, **kw):
        """
        Deal initial cards for new game.
          - s[reserves]: *rows* ea open.
          - s[rows]: 1 ea.

        .. \_start([reserves=4]) -> None
        """
        for i in range(reserves - 1):
            self.s['talon'].dealRow(rows=self.s['reserves'], frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRow(rows=self.s['reserves'])
        self.s['talon'].dealRow()

    @state.not_state('is_preview')
    def _update_text(self):
        if not self.texts['info']:
            return
        base_rank = self.s['foundations'].cap['base_rank']
        t = '' if base_rank is cs.ANY else self.RANKS[base_rank]
        self.texts['info'].config(text=t)


class DoubleFives(game.Game):
    """
    Finish cards on _foundation_ within 2 turn.

    :CVariables:
      _layout_ : pysollib.layout.Layout
        .. pysollib.layouts.freecell.DoubleFivesLayout
        - row [10 horz S reserve and foundation, vert offset]
        - talon [1 SW, no offset]
        - waste [1 SE, no offset]
        - x-waste [5 S, no offset]
        - foundation [8 horz NE, no offset]
        - reserve [2 horz NW, no offset]

    _row_
      Pile 10 horz S _foundation_. Init deal 5 card from 1st deck. Build down
      in rank by same suit wrap. Play top.

    _reserve_
      Pile 2 horz NW. Init deal 1 card from 1st deck. Build none. Play top.

    _talon_
      Pile 1 SW. 1st turn, deal 1 card on S _waste_ with cards play close on
      SE _waste_ before next deal. 2nd turn, deal 1 card on SE _waste_.

    _wastes_
      Pile 5 S, 1 SW. Build none. Play top.

    _foundation_
      Pile 8 N. Build up in rank by same suit from base. Play none.
    ------
    :cvar Stack _talon_: DoubleFives_Talon (max_rounds=2)
    :cvar Stack _waste_: Waste
    :cvar Stack _xwaste_: ReserveWaste
    :cvar Stack _foundation_: stack.Vary_SS_Foundation (max_move=0)
    :cvar Stack _row_: stack.SS_RowStack (mod=13, max_move=1)
    :cvar Stack _reserve_: stack.OpenStack
    :cvar Hint _hint_: hint.CautiousDefaultHint
    """
    __info__ = dict(id=587, name='Double Fives', game_type=GT.TWO_DECK,
                    decks=2, redeals=1, skill_level=SL.BALANCED,
                    version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _layout_ = FreeCellLayout.DoubleFivesLayout
    _talon_ = Wrap(DoubleFives_Talon, max_rounds=2)
    _wastes_ = (ReserveWaste, Waste,)
    _foundation_ = Wrap(stack.Vary_SS_Foundation, max_move=0)
    _row_ = Wrap(stack.SS_RowStack, mod=13, max_move=1)
    _reserve_ = stack.OpenStack
    _hint_ = CautiousDefaultHint

    def _create_game(self, **kw):
        """
        Create game format.

        Augment pile placements based on current form and group attributes.

        .. \_create_game(rows=10, playcards=16, xwastes=5, reserves=2,
             waste=False, xwastestext=True) -> Layout
        """
        kwdefault(kw, rows=10, playcards=16, xwastes=5, reserves=2, waste=False,
                  xwastestext=True)
        return super(DoubleFives, self)._create_game(**kw)

    def _pack_shuffle(self, cards, parts=None):
        """
        _pack_shuffle(cards:seq(Card ...)[, parts:int=NumDecks])
        -> seq(Card ...)

        Override to return given cards shuffled by deck given random.
        """
        parts = parts or self['decks']
        return super(DoubleFives, self)._pack_shuffle(cards, parts)

    def _start(self, rows=5, **kw):
        """
        Deal initial cards for new game.
          - s[rows]: *rows* ea open.
          - s[reserves]: 1 ea.

        .. \_start([rows=5]) -> None
        """
        for i in range(rows - 1):
            self.s['talon'].dealRow(frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRow()
        self.s['talon'].dealRow(rows=self.s['reserves'])

    @state.not_state('is_preview')
    def _update_text(self):
        if not self.texts['info']:
            return
        base_rank = self.s['foundations'].cap['base_rank']
        t = '' if base_rank is cs.ANY else self.RANKS[base_rank]
        self.texts['info'].config(text=t)

    def _auto_deal(self, sound=True):
        if (sum(r.cards for r in self.s['xwastes']) == 0 and
            self.s['talon'].can_deal_cards()):
            return self.talon_deal(sound=sound)
        return 0

PROVIDES = [GrandDuchess, Parisienne, GrandDuchessPlus, Dutchess, DoubleFives]